package com.gitee.wsl.ext.base

import kotlin.math.max
import kotlin.math.min
import kotlin.math.sign


fun CharProgression.to(newTo: Char) = this.first .. newTo step step

fun CharProgression.by(newStep: Int) = this.first .. last step newStep

/**
 * Returns a new interval with the from and to values reversed and the step value negated.
 */
fun CharProgression.reverseThis() = this.last .. this.first step -this.step

fun CharProgression.goForward(): Boolean {
    return this.first <= this.last && this.step > 0
}

fun CharProgression.forEachGoForward(procedure: (Char)->Unit) {
    if (this.goForward()) {
        var i = this.first
        while (i <= this.last) {
            procedure(i)
            i += this.step
        }
    } else {
        var i = this.first
        while (i >= this.last) {
            procedure(i)
            i += this.step
        }
    }
}

val CharProgression.size get() = IntervalUtils.intSize(first.code.toLong(), last.code.toLong(),step.toLong())



/**
 * Returns an Interval representing the even values from the value from to the value to.
 */
fun evensFromTo(from: Int, to: Int): IntProgression {
    var from = from
    var to = to
    if (from % 2 != 0) {
        if (from < to) {
            from++
        } else {
            from--
        }
    }
    if (to % 2 != 0) {
        if (to > from) {
            to--
        } else {
            to++
        }
    }
    return from .. to step  if (to > from) 2 else -2
}


fun IntProgression.to(newTo: Int) = this.first .. newTo step step

fun IntProgression.by(newStep: Int) = this.first .. last step newStep

/**
 * Returns a new interval with the from and to values reversed and the step value negated.
 */
fun IntProgression.reverseThis() = this.last .. this.first step -this.step

fun IntProgression.goForward(): Boolean {
    return this.first <= this.last && this.step > 0
}

fun IntProgression.forEachGoForward(procedure: (Int)->Unit) {
    if (this.goForward()) {
        var i = this.first
        while (i <= this.last) {
            procedure(i)
            i += this.step
        }
    } else {
        var i = this.first
        while (i >= this.last) {
            procedure(i)
            i += this.step
        }
    }
}

val IntProgression.size get() = IntervalUtils.intSize(first.toLong(),last.toLong(),step.toLong())


val LongProgression.size get() = IntervalUtils.intSize(first,last,step)

fun LongProgression.to(newTo: Long) = this.first .. newTo step step

fun LongProgression.by(newStep: Long) = this.first .. last step newStep

/**
 * Returns a new interval with the from and to values reversed and the step value negated.
 */
fun LongProgression.reverseThis() = this.last .. this.first step -this.step

fun LongProgression.goForward(): Boolean {
    return this.first <= this.last && this.step > 0
}

fun LongProgression.forEachGoForward(procedure: (Long)->Unit) {
    if (this.goForward()) {
        var i = this.first
        while (i <= this.last) {
            procedure(i)
            i += this.step
        }
    } else {
        var i = this.first
        while (i >= this.last) {
            procedure(i)
            i += this.step
        }
    }
}


/**
 * This is a utility class for common behaviors between Interval and IntInterval. It is
 * a public class only because Interval and IntInterval are in different packages.
 */
object IntervalUtils {
        fun checkArguments(from: Long, to: Long, stepBy: Long) {
            checkStepBy(from, to, stepBy)
            checkSize(from, to, stepBy)
        }

        private fun checkSize(from: Long, to: Long, stepBy: Long) {
            val rangeSize = (to - from) / stepBy + 1L
            require(Int.Companion.MAX_VALUE.toLong() >= rangeSize) {
                ("Range size: "
                        + rangeSize
                        + " exceeds max size() of "
                        + Int.Companion.MAX_VALUE)
            }
        }

        private fun checkStepBy(from: Long, to: Long, stepBy: Long) {
            require(stepBy != 0L) { "Cannot use a step by of 0" }
            require(!(from > to && stepBy > 0L || from < to && stepBy < 0L)) { "Step by is incorrect for the range" }
        }

        fun intSize(from: Long, to: Long, step: Long): Int {
            val result = (to - from) / step + 1L
            return result.toInt()
        }

        fun contains(value: Long, from: Long, to: Long, step: Long): Boolean {
            return isWithinBoundaries(value, from, to, step)
                    && (value - from) % step == 0L
        }

        fun isWithinBoundaries(value: Long, from: Long, to: Long, step: Long): Boolean {
            return step > 0L && from <= value && value <= to
                    || step < 0L && to <= value && value <= from
        }

        fun indexOf(value: Long, from: Long, to: Long, step: Long): Int {
            if (!isWithinBoundaries(value, from, to, step)) {
                return -1
            }
            val diff = value - from
            if (diff % step == 0L) {
                return (diff / step).toInt()
            }
            return -1
        }

        fun valueAtIndex(index: Int, from: Long, to: Long, step: Long): Long {
            if (index <= 0) {
                return from
            }
            val value = from + step * index.toLong()
            if (step > 0L) {
                return min(value, to)
            }
            return max(value, to)
        }

        fun binarySearch(value: Long, from: Long, to: Long, step: Long): Int {
            if (step > 0L && from > value || step < 0L && from < value) {
                return -1
            }

            if (step > 0L && to < value || step < 0 && to > value) {
                return -1 - intSize(from, to, step)
            }

            val diff = value - from
            val index = (diff / step).toInt()
            return if (diff % step == 0L) index else (index + 2) * -1
        }

        fun calculateAdjustedStep(from: Int, to: Int, stepBy: Int): Int {
            val direction: Int = to - from.sign
            return if (direction == 0) stepBy else direction * stepBy
        }
}
